Skip to content

如何判断元素是否在可视区域中

判断元素是否在可视区域(视口)内是一个非常常见的需求(比如懒加载、埋点统计、滚动动画触发等)

实现的方案

shell
共有三种方案实现,分别是:IntersectionObserver API(现代方案,推荐)、getBoundingClientRect 传统方案,兼容性好)、offsetTop + 滚动距离(冷门方案,不推荐)

IntersectionObserver API 方案 - 视口观察器

shell
# IntersectionObserver API 是什么
是浏览器原生提供的异步监听元素可见性的 API,性能最优(不会在滚动时频繁触发计算),也是目前最推荐的方案。
IntersectionObserver 是构造函数,第一个参数为回调函数,第二个参数为配置对象

# 优点
IntersectionObserver 是异步的,不会阻塞主线程,滚动时性能远优于传统的滚动监听 + 计算方案;

# 原理
IntersectionObserver 构造函数的第一个回调函数中,可获取到元素进入和离开可视区域的时机,第二个参数配置对象:可指定监听的容器、距可视区距离、元素可见性等
词法专门为观察 元素是否进入可视区域 而生

# 代码示例
# 1. 创建观察器实例
const observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    # entry.isIntersecting 为 true 时,表示元素进入可视区域
    if (entry.isIntersecting) {
      console.log('元素进入可视区域', entry.target);
      # 可在这里执行业务逻辑:比如加载图片、触发动画、统计曝光等
      # 如果只需要监听一次,可取消观察
      # observer.unobserve(entry.target);
    } else {
      console.log('元素离开可视区域', entry.target);
    }
  });
}, {
  # 可选配置项(按需调整)
  root: null, # 监听的根容器,默认是视口(null)
  rootMargin: '0px', # 可视区域的扩展/收缩(比如 '100px' 表示元素距离视口100px时就触发)
  threshold: 0 # 触发回调的可见比例(0=只要有1像素可见就触发;1=元素完全可见才触发)
});

# 2. 监听目标元素
const targetElement = document.querySelector('#your-target-element');
if (targetElement) {
  observer.observe(targetElement);
}

# 3. 销毁观察器(比如组件卸载时)
# observer.disconnect();

getBoundingClientRect 方案 - 获取边界客户端矩形

shell
# getBoundingClientRect
此方案是通过计算元素的位置与视口的关系来判断,兼容性覆盖所有浏览器,但需要结合 scroll 事件监听,滚动时频繁触发会有性能风险(建议加节流)。
getBoundingClientRect dom 元素的一个属性,获取元素的边界位置,返回元素的 top/left/bottom/right/width/height,都是相对于视口左上角的坐标(最后一句很关键)

# 优点
兼容旧版本浏览器

# 原理
getBoundingClientRect 可获取元素的边界位置相对左上角的坐标信息 可以此判断元素是否进入可视区
还可以获取 视口的宽高 结合 getBoundingClientRect 信息判断元素是否整体进入可视区,还是部分进入可视区


# 代码示例
function isElementInViewport(el) {
  if (!el) return false;
  
  # 获取元素的top/left/bottom/right/width/height,都是相对于视口左上角的坐标
  const rect = el.getBoundingClientRect();
  
  # 视口的宽高(兼容移动端)
  const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
  const viewportHeight = window.innerHeight || document.documentElement.clientHeight;

  # 判断逻辑:元素的四个边都在视口内(可根据需求调整,比如只要部分可见)
  return (
    rect.top >= 0 &&          # 元素顶部不超出视口上沿
    rect.left >= 0 &&         # 元素左侧不超出视口左沿
    rect.bottom <= viewportHeight && # 元素底部不超出视口下沿
    rect.right <= viewportWidth      # 元素右侧不超出视口右沿
  );
}

# 2. 滚动监听(加节流优化性能)
let timer = null;
window.addEventListener('scroll', () => {
  clearTimeout(timer);
  timer = setTimeout(() => {
    const target = document.querySelector('#your-target-element');
    const isInViewport = isElementInViewport(target);
    console.log('元素是否在可视区域:', isInViewport);
  }, 100); # 100ms 节流,减少计算次数
});

offsetTop + 滚动距离(冷门方案,不推荐)方案

shell
# 原理
滚动距离、视口高度/宽度、 元素相对于文档的偏移量 以此判断元素是否出现在可视区域
这是早期的手动计算方案,需要逐层计算元素的偏移量,容易出错(比如有定位父元素时),仅作为兼容性兜底

function isInViewportByOffset(el) {
  if (!el) return false;
  
  # 滚动距离
  const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
  const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
  
  # 视口高度/宽度
  const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
  const viewportWidth = window.innerWidth || document.documentElement.clientWidth;
  
  # 元素相对于文档的偏移量(需逐层计算,这里简化为直接取offsetTop,仅适用于无定位父元素)
  const elTop = el.offsetTop;
  const elLeft = el.offsetLeft;
  const elHeight = el.offsetHeight;
  const elWidth = el.offsetWidth;

  # 判断逻辑
  return (
    elTop + elHeight > scrollTop &&        # 元素底部 > 滚动顶部
    elTop < scrollTop + viewportHeight &&  # 元素顶部 < 滚动顶部+视口高度
    elLeft + elWidth > scrollLeft &&       # 元素右侧 > 滚动左侧
    elLeft < scrollLeft + viewportWidth    # 元素左侧 < 滚动左侧+视口宽度
  );
}